/**   
 * Copyright 2011 COMERE Harold
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package etrl.geometry;

import java.util.ArrayList;

import com.google.gwt.user.client.Window;
import com.googlecode.gwtgl.array.Float32Array;
import com.googlecode.gwtgl.array.Uint16Array;
import com.googlecode.gwtgl.binding.WebGLBuffer;
import com.googlecode.gwtgl.binding.WebGLRenderingContext;

import etrl.core.BoundingBox;
import etrl.core.Engine;

/**
 * \class Drawable
 * \brief Geometry object.
 * @author harold
 *
 */
public class Drawable
{
	/**
	 * GL buffer object for vertice
	 */
	private WebGLBuffer _verticeBuffer;
	
	/**
	 * GL buffer object for indice
	 */
	private WebGLBuffer _indiceBuffer;

	/**
	 * Array of geometry vertice
	 */
	private VertexArray _vertexArray;
	
	/**
	 * Array of geometry indice
	 */
	private IndexArray _indexArray;
	
	/**
	 * Flag indicating if the geometry is fully defined, ie has a vertex and index array.
	 */
	private boolean _ready;
	
	/**
	 * Geometry primitive kind
	 */
	private PrimitiveKind _primitiveKind;
	
	/**
	 * GL context object.
	 */
	private WebGLRenderingContext _gl;

	/**
	 * JAVA size of float ...
	 */
	private final int SIZE_OF_FLOAT = Float.SIZE / 8;

	/**
	 * \brief Initializes an empty drawable
	 * @param[in] primitiveKind Drawable primitive kind to use.
	 */
	public Drawable(PrimitiveKind primitiveKind)
	{
		_gl = Engine.getInstance().getGL();
		_vertexArray = null;
		_indexArray = null;
		_ready = false;
		
		_primitiveKind = primitiveKind;

		_verticeBuffer = _gl.createBuffer();
		_indiceBuffer = _gl.createBuffer();
	}

	/**
	 * \brief Render the drawable geometry 
	 */
	public void drawGeometry()
	{
		if(_ready)
		{
			_gl.enableVertexAttribArray(0);
			_gl.enableVertexAttribArray(1);
			_gl.enableVertexAttribArray(2);
			_gl.enableVertexAttribArray(3);
	
			_gl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, _verticeBuffer);
			_gl.vertexAttribPointer(0, 4, WebGLRenderingContext.FLOAT, false, SIZE_OF_FLOAT * 13, 0);
			_gl.vertexAttribPointer(1, 4, WebGLRenderingContext.FLOAT, false, SIZE_OF_FLOAT * 13, SIZE_OF_FLOAT * 4);
			_gl.vertexAttribPointer(2, 3, WebGLRenderingContext.FLOAT, false, SIZE_OF_FLOAT * 13, SIZE_OF_FLOAT * 8);
			_gl.vertexAttribPointer(3, 2, WebGLRenderingContext.FLOAT, false, SIZE_OF_FLOAT * 13, SIZE_OF_FLOAT * 11);
	
			_gl.bindBuffer(WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, _indiceBuffer);
			
			// Make the adequat drawElement call according to the drawable's primitive kind 
			switch(_primitiveKind)
			{
				case LINES :
					_gl.drawElements(WebGLRenderingContext.LINES, _indexArray.size(), WebGLRenderingContext.UNSIGNED_SHORT, 0);					
				break;
			
				case TRIANGLE_STRIP :
					_gl.drawElements(WebGLRenderingContext.TRIANGLE_STRIP, _indexArray.size(), WebGLRenderingContext.UNSIGNED_SHORT, 0);
				break;
			
				case TRIANGLES :
					_gl.drawElements(WebGLRenderingContext.TRIANGLES, _indexArray.size(), WebGLRenderingContext.UNSIGNED_SHORT, 0);
				break;
			}
			
			
			_gl.disableVertexAttribArray(0);
			_gl.disableVertexAttribArray(1);
			_gl.disableVertexAttribArray(2);
			_gl.disableVertexAttribArray(3);
		}
	}
	
	/**
	 * \brief Sets the vertex array to use.
	 * @param[in] vertexArray vertex array to use.
	 */
	public void setVertexArray(VertexArray vertexArray)
	{
		_vertexArray = vertexArray;
		_gl.bindBuffer(WebGLRenderingContext.ARRAY_BUFFER, _verticeBuffer);
		_gl.bufferData(WebGLRenderingContext.ARRAY_BUFFER, Float32Array.create(_vertexArray.toFloatArray()), WebGLRenderingContext.STATIC_DRAW);
		
		if(_indexArray != null)
			_ready = true;
	}
	
	/**
	 * \brief Sets the index array to use.
	 * @param[in] indexArray index array to use.
	 */
	public void setIndexArray(IndexArray indexArray)
	{
		_indexArray = indexArray;
		_gl.bindBuffer(WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, _indiceBuffer);
		_gl.bufferData(WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, Uint16Array.create(_indexArray.toIntArray()), WebGLRenderingContext.STATIC_DRAW);
	
		if(_vertexArray != null)
			_ready = true;
	}
	
	/**
	 * \brief Computes the drawable bounding box
	 * @return Drawable bounding box.
	 */
	public BoundingBox getBoundingBox()
	{
		if(_vertexArray != null)
			return _vertexArray.getBoundingBox();
		else
		{
			System.out.println("[Drawable::getBoundingBox] Unexpected error...");
			return new BoundingBox();
		}
	}
	
	/**
	 * \brief Lets the given triangle visitor visiting each geometry triangle.
	 * @param[in] visitor Visitor needing to visit triangles.
	 */
	public void accept(TriangleVisitor visitor)
	{
		ArrayList <Integer> indice = _indexArray.getIndice();
		ArrayList <Vertex> vertice = _vertexArray.getVertice();
		int primitiveCount;
		
		// Only work with triangles
		switch(_primitiveKind)
		{
			case TRIANGLES :
				// Each triangle is defined by 3 separated points, so the indice count must be multiple of 3
				if(indice.size() % 3 == 0)
				{
					primitiveCount = indice.size() / 3;
					
					for(int i = 0; i < primitiveCount; ++i)
					{
						visitor.visit(vertice.get(indice.get(3 * i))._coordinate, vertice.get(indice.get(3 * i + 1))._coordinate, vertice.get(indice.get(3 * i + 2))._coordinate);
					}
				
				} // else there is an error anywhere o.O
				else
					Window.alert("[Dawable::accept] Invalid indice count for TRIANGLES primitives.");
			break;
			
			case TRIANGLE_STRIP :
				// The first triangle is defined by 3 points, then the 2 prev points + the next one define next triangle.
				// So it must have at least 3 indice
				if(indice.size() >= 3)
				{
					primitiveCount = indice.size() - 2;
					boolean firstTriangle = true;
					int prev = 1, prevprev = 0;
					
					for(int i = 0; i < primitiveCount; ++i)
					{
						if(firstTriangle)
						{
							visitor.visit(vertice.get(indice.get(0))._coordinate, vertice.get(indice.get(1))._coordinate, vertice.get(indice.get(2))._coordinate);	
							firstTriangle = false;
						}
						else
							visitor.visit(vertice.get(indice.get(prevprev))._coordinate, vertice.get(indice.get(prev))._coordinate, vertice.get(indice.get(prev + 1))._coordinate);							
						
						++prevprev;
						++prev;
					}
				
				} // else there is an error anywhere o.O
			break;
			
			// TODO : TRIANGLE_FAN
			
			default :
				// Other primitive kinds cannot be visited
			break;
		}
		
	}
}
